package util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public abstract class Main {

	protected final static int NUMBER_OF_INTERATIONS = 1000;
	protected final static int NUMBER_OF_REPETITIONS = 500;

	protected final static String FILE_SEPARATOR = File.separator;

	public void runExperiment(int sizeOfTable, int numberOfThreads,
			double modifyThreadsPercentage) throws InterruptedException {

		int counter = 0;
		List<Long> results = new ArrayList<Long>();

		while (counter < NUMBER_OF_REPETITIONS) {

			Table table = new Table(sizeOfTable);

			List<Thread> threadList = new ArrayList<Thread>();

			initializeList(table, threadList, numberOfThreads,
					modifyThreadsPercentage);

			Long initialTime = System.nanoTime();

			initializeThreads(threadList);

			Long totalTime = System.nanoTime() - initialTime;

			results.add(totalTime);
			counter++;
		}

		try {
			saveAveragesOnFile(results, "results" + FILE_SEPARATOR
					+ getFileName(), modifyThreadsPercentage);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	protected abstract String getFileName();

	protected void saveAveragesOnFile(List<Long> values, String fileName,
			double modifyThreadsPercentage) throws IOException {
		File file = new File(fileName);
		FileOutputStream fileOutputStream = new FileOutputStream(file);

		long average = calculateAverage(values);
		long variance = calculateVariance(values, average);
		double standardDeviation = Math.sqrt(variance);

		writeResults(fileOutputStream, average, variance, standardDeviation,
				modifyThreadsPercentage);

		System.out.println("Finished!");
	}

	private static void writeResults(FileOutputStream fileOutputStream,
			long average, long variance, double standardDeviation,
			double modifyThreadsPercentage) throws IOException {
		fileOutputStream.write(("\nWith percentage change : "
				+ modifyThreadsPercentage + "\n").getBytes());
		fileOutputStream.write(("\nAverage : " + average + "\n").getBytes());
		fileOutputStream.write(("Variance : " + variance + "\n").getBytes());
		fileOutputStream
				.write(("Standard Deviation : " + standardDeviation + "\n")
						.getBytes());
		fileOutputStream.close();
	}

	private long calculateVariance(List<Long> values, long average) {
		long sum = 0;

		for (Long value : values) {
			sum += Math.pow(value - average, 2);
		}

		return sum / values.size();
	}

	private long calculateAverage(List<Long> values) {
		long sumOfValues = 0;

		for (Long value : values) {
			sumOfValues += value;
		}

		return sumOfValues / values.size();
	}

	public void initializeThreads(List<Thread> listOfThreads)
			throws InterruptedException {
		for (Thread thread : listOfThreads) {
			thread.start();
		}

		for (Thread thread : listOfThreads) {
			thread.join();
		}
	}

	public void initializeList(Table table, List<Thread> listOfThreads,
			int numberOfThreads, double modifyThreadsPercentage) {
		//initializeProducerThreads(table, listOfThreads, numberOfThreads);
		initializeConsumerThreads(table, listOfThreads, numberOfThreads, modifyThreadsPercentage);
		initializeModifierThreads(table, listOfThreads, numberOfThreads, modifyThreadsPercentage);
	}

	protected void initializeProducerThreads(Table table,
			List<Thread> listOfThreads, int numberOfThreads, int time) {
		for (int i = 0; i < numberOfThreads; i++) {
			Thread t1 = createProducerThread(table, NUMBER_OF_INTERATIONS);
			listOfThreads.add(t1);
		}
	}

	protected void initializeConsumerThreads(Table table,
			List<Thread> listOfThreads, int numberOfThreads,
			double modifyThreadsPercentage) {
		for (int i = 0; i < numberOfThreads; i++) {
			Thread t1 = createConsumerThread(table, NUMBER_OF_INTERATIONS);
			listOfThreads.add(t1);
		}
	}

	protected void initializeModifierThreads(Table table,
			List<Thread> listOfThreads, int numberOfThreads,
			double modifyThreadsPercentage) {
		for (int i = 0; i < numberOfThreads; i++) {
			Thread t1 = createModifierThread(table,
					(int) (NUMBER_OF_INTERATIONS * modifyThreadsPercentage));
			listOfThreads.add(t1);
		}
	}

	public abstract Thread createModifierThread(Table table, int numberOfInterations);

	public abstract Thread createConsumerThread(Table table, int numberOfInterations);

	public abstract Thread createProducerThread(Table table, int numberOfInterations);

}
